/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 * Description: emmc phy
 * Author:
 * Create: 2022/08/13
 */
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/bitfield.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include "sdhci-pltfm.h"
#include "sdhci_zodiac_mmc.h"
#include "emmc_phy.h"

/* emmc phy register */
#define PHY_PHYINITCTRL 0x4
#define INIT_EN 0x1
#define DLYMEAS_EN (0x1 << 2)
#define ZCAL_EN (0x1 << 3)
#define BIST_CLK (0x1 << 17)
#define PHY_PHYINITSTAT 0x8
/* This register specify the control the ZQ calibration */
#define PHY_IMPCTRL 0x24
#define IMPCTRL_ZCOM_RSP_DLY_MASK 0x3F

/* This register specify the ZQ calibration result */
#define PHY_IMPSTATUS 0x28
#define PHY_DATENA_DLY 0x23C
#define PHY_PHYCTRL2 0x248
#define PHY_DLYMEAS_UPDATE (0x1 << 14)
#define PHY_DLYMEAS_1T_SEL (0x1 << 21)
#define PHY_DLY_CTL 0x250
#define DLY_CODE_1T_MASK 0x7FF
#define PHY_DLY_CTL1 0x254
#define DLY_3_CODE_MASK 0x3FF
#define DLY_3_CODE_SHIFT 8
#define DLY_2_CODE_MASK 0x7FF
#define DLY_2_CODE_SHIFT 8
#define DLY_1_CODE_MASK 0x3FF
#define DLY_1_CODE_SHIFT 21
#define INV_TX_CLK (1UL << 31)

#define PHY_DLY_CTL2 0x258
#define PHY_IOCTL_PUPD 0x260
#define PUPD_EN_DATA 0xFF
#define PUPD_EN_STROBE 0x100
#define PUPD_EN_CMD 0x200
#define PULLUP_DATA (0xFF << 16)
#define PULLUP_CMD (0x200 << 16)
#define PHY_IOCTL_RONSEL_1 0x264
#define PHY_IOCTL_RONSEL_2 0x268
#define EMMC_RONSEL_2 0x7FF
#define PHY_IOCTL_IOE 0x26c
#define DA_EMMC_E (0x1 << 10)
#define DA_EMMC_IE (0x7FF << 16)
#define PHY_IOCTL_INMODE 0x28c
#define RG_EMMC_INMODE 0x7FF
#define WAIT_DONE_MAX_TIMES 30

static inline void sdhci_phy_writel(struct sdhci_host *host, u32 val, u32 reg)
{
    struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
    struct sdhci_zodiac_host *zodiac_host = sdhci_pltfm_priv(pltfm_host);
    writel(val, zodiac_host->phy.io_base + reg);
}

static inline u32 sdhci_phy_readl(struct sdhci_host *host, u32 reg)
{
    u32 ret;
    struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
    struct sdhci_zodiac_host *zodiac_host = sdhci_pltfm_priv(pltfm_host);
    ret = readl(zodiac_host->phy.io_base + reg);
    return ret;
}

void sdhci_zodiac_dump_phy_regs(struct sdhci_host *host)
{
    pr_err("Start dump emmc phy register\n");
    pr_err("==================== emmc phy ====================");
    pr_err("PHY init ctrl: 0x%08x | init stat: 0x%08x\n", sdhci_phy_readl(host, PHY_PHYINITCTRL),
        sdhci_phy_readl(host, PHY_PHYINITSTAT));
    pr_err("PHY imp ctrl: 0x%08x | imp stat: 0x%08x\n", sdhci_phy_readl(host, PHY_IMPCTRL),
        sdhci_phy_readl(host, PHY_IMPSTATUS));
    pr_err("PHY dly ctrl: 0x%08x\n", sdhci_phy_readl(host, PHY_DLY_CTL));
    pr_err("PHY dly ctrl1: 0x%08x | dly ctrl2: 0x%08x\n", sdhci_phy_readl(host, PHY_DLY_CTL1),
        sdhci_phy_readl(host, PHY_DLY_CTL2));
    pr_err("PHY drv1: 0x%08x | drv2: 0x%08x\n", sdhci_phy_readl(host, PHY_IOCTL_RONSEL_1),
        sdhci_phy_readl(host, PHY_IOCTL_RONSEL_2));
}

static int sdhci_combo_phy_zq_cal(struct sdhci_host *host)
{
    u32 count;
    u32 reg_val;

    reg_val = sdhci_phy_readl(host, PHY_IMPCTRL);
    reg_val &= ~(IMPCTRL_ZCOM_RSP_DLY_MASK);
    /* zcom_rsp_dly set as d'60 */
    reg_val |= (IMPCTRL_ZCOM_RSP_DLY_MASK & 0x3C);
    sdhci_phy_writel(host, reg_val, PHY_IMPCTRL);

    reg_val = sdhci_phy_readl(host, PHY_PHYINITCTRL);
    /* zcal_en set 1 to start impedance cal */
    reg_val |= ZCAL_EN;
    /* disable delay measuremnt to wait zq calc ready */
    reg_val &= ~DLYMEAS_EN;
    /* int_en & zcal_en all set to 1 */
    reg_val |= INIT_EN;
    sdhci_phy_writel(host, reg_val, PHY_PHYINITCTRL);

    count = 0;
    do {
        if (count > WAIT_DONE_MAX_TIMES) { /*  delay total 3000us */
            pr_err("phy_zq_cal wait timeout\n");
            pr_err("PHY init stat: 0x%08x\n", sdhci_phy_readl(host, PHY_PHYINITSTAT));
            pr_err("the IMPSTATUS is %x\n", sdhci_phy_readl(host, PHY_IMPSTATUS));
            return -ETIMEDOUT;
        }
        udelay(100); // 100us
        count++;
        reg_val = sdhci_phy_readl(host, PHY_PHYINITCTRL);
    } while (reg_val & INIT_EN);

    return 0;
}

int sdhci_combo_phy_init(struct sdhci_host *host)
{
    u32 reg_val;
    int ret;

    // close bist clk
    reg_val = sdhci_phy_readl(host, PHY_PHYINITCTRL);
    reg_val |= BIST_CLK;
    sdhci_phy_writel(host, reg_val, PHY_PHYINITCTRL);
    // close data enable delay
    sdhci_phy_writel(host, 0, PHY_DATENA_DLY);

    reg_val = PUPD_EN_DATA | PUPD_EN_STROBE | PUPD_EN_CMD | PULLUP_DATA | PULLUP_CMD;
    // pad pull up,clk na,strb pull down,cmd/data pull up
    sdhci_phy_writel(host, reg_val, PHY_IOCTL_PUPD);

    /* Output impedance 50ohm */
    reg_val = sdhci_phy_readl(host, PHY_IOCTL_RONSEL_2);
    reg_val |= EMMC_RONSEL_2;
    sdhci_phy_writel(host, reg_val, PHY_IOCTL_RONSEL_2);

    /* enable phy input output */
    reg_val = sdhci_phy_readl(host, PHY_IOCTL_IOE);
    reg_val |= DA_EMMC_E | DA_EMMC_IE;
    sdhci_phy_writel(host, reg_val, PHY_IOCTL_IOE);

    reg_val = sdhci_phy_readl(host, PHY_IOCTL_INMODE);
    reg_val &= ~RG_EMMC_INMODE;
    sdhci_phy_writel(host, reg_val, PHY_IOCTL_INMODE);

    // phy tx clk negate
    reg_val = sdhci_phy_readl(host, PHY_DLY_CTL1);
    reg_val |= INV_TX_CLK;
    sdhci_phy_writel(host, reg_val, PHY_DLY_CTL1);

    /* do ZQ Calibration and so on, to be finish */
    ret = sdhci_combo_phy_zq_cal(host);
    if (ret != 0) {
        pr_err("sdhci_combo_phy_zq_cal fail!\r\n");
        return ret;
    }
    return 0;
}

static inline u32 sdhci_get_phy_delay1(u32 ratio, u32 margin, u32 delaymeas_code)
{
    if (ratio != 0) {
        return ((delaymeas_code / ratio) & 0x7) + ((delaymeas_code / ratio) & 0x3F8) + margin;
    } else {
        return margin;
    }
}

static void sdhci_config_phy_delay_ctl(struct sdhci_host *host, struct mmc_ios *ios)
{
    struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
    struct sdhci_zodiac_host *zodiac_host = sdhci_pltfm_priv(pltfm_host);
    u32 reg_val;
    u32 delaymeas_code;
    u32 delay1;
    u32 delay2;
    u32 delay3;

    reg_val = sdhci_phy_readl(host, PHY_DLY_CTL);
    delaymeas_code = reg_val & DLY_CODE_1T_MASK;
    reg_val = sdhci_phy_readl(host, PHY_DLY_CTL1);
    reg_val &= ~(DLY_1_CODE_MASK << DLY_1_CODE_SHIFT);

    // set tx_clk
    if (ios->timing == MMC_TIMING_MMC_HS200) {
        delay1 = sdhci_get_phy_delay1(zodiac_host->delay1_cfg[ZODIAC_EMMC_HS200].ratio,
            zodiac_host->delay1_cfg[ZODIAC_EMMC_HS200].margin, delaymeas_code);
    } else if (ios->timing == MMC_TIMING_MMC_HS400) {
        delay1 = sdhci_get_phy_delay1(zodiac_host->delay1_cfg[ZODIAC_EMMC_HS400].ratio,
            zodiac_host->delay1_cfg[ZODIAC_EMMC_HS400].margin, delaymeas_code);
    } else if (ios->timing == MMC_TIMING_MMC_DDR52) {
        delay1 = sdhci_get_phy_delay1(zodiac_host->delay1_cfg[ZODIAC_EMMC_DDR52].ratio,
            zodiac_host->delay1_cfg[ZODIAC_EMMC_DDR52].margin, delaymeas_code);
    }

    reg_val |= ((DLY_1_CODE_MASK & (delay1)) << DLY_1_CODE_SHIFT);
    sdhci_phy_writel(host, reg_val, PHY_DLY_CTL1);

    // DDR52 no tuning need config delay2 ，hs200 and hs400 tuning no need config delay2
    if (ios->timing == MMC_TIMING_MMC_DDR52) {
        reg_val = sdhci_phy_readl(host, PHY_DLY_CTL2);
        reg_val &= ~(DLY_2_CODE_MASK << DLY_2_CODE_SHIFT);
        delay2 = delaymeas_code / 2; // DDR52 1/2T, 1/2T measure, finally 1/4T
        reg_val |= ((DLY_2_CODE_MASK & (delay2)) << DLY_2_CODE_SHIFT);
        sdhci_phy_writel(host, reg_val, PHY_DLY_CTL2);
    } else if (ios->timing == MMC_TIMING_MMC_HS400) {
        // HS400 need config delay3
        reg_val = sdhci_phy_readl(host, PHY_DLY_CTL1);
        reg_val &= ~(DLY_3_CODE_MASK << DLY_3_CODE_SHIFT);
        delay3 = ((delaymeas_code / 4) & 0x7) + ((delaymeas_code / 4) & 0x3F8);
        reg_val |= ((DLY_3_CODE_MASK & (delay3)) << DLY_3_CODE_SHIFT);
        sdhci_phy_writel(host, reg_val, PHY_DLY_CTL1);
    }
}

int sdhci_combo_phy_delay_measurement(struct sdhci_host *host, struct mmc_ios *ios)
{
    u32 count;
    u32 reg_val;

    reg_val = sdhci_phy_readl(host, PHY_PHYCTRL2);
    if (ios->timing == MMC_TIMING_MMC_DDR52) {
        reg_val &= ~PHY_DLYMEAS_1T_SEL; // ddr52 1/2T measure
    } else {
        reg_val |= PHY_DLYMEAS_1T_SEL;
    }
    /* phy_dlymeas_update on */
    reg_val |= PHY_DLYMEAS_UPDATE;
    sdhci_phy_writel(host, reg_val, PHY_PHYCTRL2);

    reg_val = sdhci_phy_readl(host, PHY_PHYINITCTRL);
    reg_val &= ~ZCAL_EN;
    reg_val |= (INIT_EN | DLYMEAS_EN);
    sdhci_phy_writel(host, reg_val, PHY_PHYINITCTRL);
    count = 0;
    do {
        if (count > WAIT_DONE_MAX_TIMES) { /* delay total 3000us */
            pr_err("wait for delay measurement done timeout\n");
            return -ETIMEDOUT;
        }
        udelay(100); // 100us
        count++;
        reg_val = sdhci_phy_readl(host, PHY_PHYINITCTRL);
    } while (reg_val & INIT_EN);

    // config delay 1 2 3
    sdhci_config_phy_delay_ctl(host, ios);
    reg_val = sdhci_phy_readl(host, PHY_PHYCTRL2);
    /* phy_dlymeas_update off at dlymeas_done */
    reg_val &= ~PHY_DLYMEAS_UPDATE;
    sdhci_phy_writel(host, reg_val, PHY_PHYCTRL2);
    return 0;
}
